/**
 * 碰撞控制器 - WASD移动、拖动鼠标旋转镜头，与 TourControls 很像，但添加了像物理引擎一样的碰撞效果
 * 人体模型：胶囊体，半径0.3m，人高1.6m，视线高1.5m，胶囊中部圆柱高1.0m
 *
 * 功能
 *  - 辅助器
 *  - 复杂模型剔除机制：为它添加一个碰撞盒，并为盒子设置一个特殊的名字
 *
 * 用法
 * 1. 导入
 * import CollideControls from '@/scripts/three/CollideControls'
 * 2. 声明
 * const controls = new CollideControls(camera, renderer.domElement)
 * 3. 烘焙八叉树（在场景加载完成之后）
 * controls.bake(object)
 * 4. 更新（在渲染循环内）
 * controls.update()
 *
 * TODO
 *  [ ] 设置：初始位置设置
 *  [ ] 点位拾取后，世界会上下颠倒，应该是与改变了相机旋转的 order 导致的
 *  [ ] 与点位动画有冲突，现在没有 target 了，就无法控制朝向了
 *  [ ] 与点位动画有冲突，因为物理引擎认为人不能飞到天上去
 */
import { THREE, Stats, OrbitControls, gsap } from '@/scripts/three/toolchain'
import { Octree } from 'three/examples/jsm/math/Octree.js'
import { OctreeHelper } from 'three/examples/jsm/helpers/OctreeHelper.js'
import { Capsule } from 'three/examples/jsm/math/Capsule.js'

type Number5 = [number, number, number, number, number]
import CameraNavigator from './CameraNavigator'

type CollideControlsOptions = {
  enableJump?: boolean // 启用跳跃功能，默认 true
}

// 常量
const GRAVITY = 20 // 重力参数
const JUMP_VELOCITY = 10 // 跳跃速度/高度，参考值：15=3m, 13=2m, 10=1m，8=0.5
const distEyeToTop = 0.1 // 眼到头顶的距离
const playerRadius = 0.3 // 玩家胶囊体的半径
const playerHeight = 1.6 // 玩家高度（取女人的平均高度，是为了综合男人和儿童）

// 变量
const cameraDirection = new THREE.Vector3()
const up = new THREE.Vector3(0, 1, 0)
const cameraRight = new THREE.Vector3()
const colliderEndToEye = new THREE.Vector3(0, playerRadius - distEyeToTop)
const eyePos = new THREE.Vector3()

export default class CollideControls {
  // 主属性
  enabled = true // 是否启用
  enableJump = true // 是否启用跳跃功能
  // 关联对象
  camera: THREE.PerspectiveCamera
  cameraNavigator: CameraNavigator
  dom: HTMLElement
  constructor(
    camera: THREE.PerspectiveCamera,
    dom: HTMLElement,
    options: CollideControlsOptions = {},
  ) {
    this.camera = camera
    this.cameraNavigator = new CameraNavigator(this.camera)
    this.dom = dom
    this.camera.rotation.order = 'YXZ'
    // 选项
    if (options.enableJump != null) this.enableJump = options.enableJump

    // 鼠标
    this.onMouseDown = this._onMouseDown.bind(this)
    this.onMouseUp = this._onMouseUp.bind(this)
    this.onMouseMove = this._onMouseMove.bind(this)
    this.dom.addEventListener('mousedown', this.onMouseDown)
    this.dom.addEventListener('mouseup', this.onMouseUp)
    this.dom.addEventListener('mousemove', this.onMouseMove)
    // 键盘
    this.onKeyDown = this._onKeyDown.bind(this)
    this.onKeyUp = this._onKeyUp.bind(this)
    document.body.addEventListener('keydown', this.onKeyDown)
    document.body.addEventListener('keyup', this.onKeyUp)
  }

  // 物理
  clock = new THREE.Clock()
  worldOctree = new Octree()
  playerCollider = new Capsule(
    new THREE.Vector3(0, playerRadius, 0),
    new THREE.Vector3(0, playerHeight - playerRadius, 0),
    playerRadius,
  )
  playerVelocity = new THREE.Vector3()
  playerDirection = new THREE.Vector3()
  playerOnFloor = false
  helper = new OctreeHelper(this.worldOctree) // 世界八叉树的辅助器，默认隐藏，需要请手动添加到场景并设置visible

  // 飞行到（兼容特写功能）
  flying = false
  flyTo(cameraPos: Number5) {
    this.flying = true
    this.cameraNavigator.flyCameraTo(...cameraPos, 2)
    setTimeout(() => {
      this.teleportPlayerTo(
        this.camera.position.x,
        this.camera.position.y,
        this.camera.position.z,
        cameraPos[3],
        cameraPos[4],
      )
      this.flying = false
    }, 1000 * 2)
  }

  // 烘焙八叉树
  baked = false
  world?: THREE.Object3D
  bake(world: THREE.Object3D) {
    this.baked = false
    console.log(`[CollideControls] 开始烘焙...`)
    const timeStart = Date.now()
    this._removeColliders(world)
    this.worldOctree.fromGraphNode(world)
    this._restoreColliders(world)
    this.helper.update()
    const timeEnd = Date.now()
    const elapsedTime = (timeEnd - timeStart) / 1000
    console.log(`[CollideControls] 烘焙完成，共耗时 ${elapsedTime} 秒`)
    this.baked = true
    this.world = world
  }

  // 烘焙剔除机制：临时移除碰撞体（name: Collider_）的子节点
  colliderChildren: { [name: string]: THREE.Object3D[] } = {}
  _removeColliders(object: THREE.Object3D) {
    object.traverse((_node) => {
      if (_node.name.startsWith('Collider_')) {
        this.colliderChildren[_node.name] = [..._node.children]
        _node.clear()
        // 顺便把半透改为全透
        const node = _node as THREE.Mesh
        const material = node.material as THREE.MeshStandardMaterial
        if (material) material.opacity = 0
      }
    })
  }
  _restoreColliders(object: THREE.Object3D) {
    object.traverse((node) => {
      if (node.name.startsWith('Collider_')) {
        const children = this.colliderChildren[node.name]
        children.forEach((child) => {
          node.add(child)
        })
      }
    })
  }

  // 调试：显示/隐藏空气墙
  showAirWalls() {
    this.setAirWalls(0.25, 0x0080ff)
  }
  hideAirWalls() {
    this.setAirWalls(0, 0xffffff)
  }
  setAirWalls(opacity: number, color: number) {
    if (this.world == null) return
    this.world.traverse((_node) => {
      if (_node.name.startsWith('Collider_')) {
        const node = _node as THREE.Mesh
        const material = node.material as THREE.MeshStandardMaterial
        if (material) {
          material.opacity = opacity
          material.color.set(color)
        }
      }
    })
  }

  // 玩家与世界碰撞
  playerCollisions() {
    const result = this.worldOctree.capsuleIntersect(this.playerCollider)
    this.playerOnFloor = false
    if (result) {
      this.playerOnFloor = result.normal.y > 0
      if (!this.playerOnFloor) {
        this.playerVelocity.addScaledVector(result.normal, -result.normal.dot(this.playerVelocity))
      }
      this.playerCollider.translate(result.normal.multiplyScalar(result.depth))
    }
  }

  // 更新控制
  updateControls(deltaTime: number) {
    // 更新前和右向量
    cameraDirection.set(0, 0, -1)
    cameraDirection.applyQuaternion(this.camera.quaternion).normalize()
    cameraRight.crossVectors(cameraDirection, up).normalize()

    const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8)
    // 前后左右
    if (this.pressedKeyMap['w']) {
      this.playerVelocity.add(cameraDirection.multiplyScalar(speedDelta))
    }
    if (this.pressedKeyMap['s']) {
      this.playerVelocity.add(cameraDirection.multiplyScalar(-speedDelta))
    }
    if (this.pressedKeyMap['a']) {
      this.playerVelocity.add(cameraRight.multiplyScalar(-speedDelta))
    }
    if (this.pressedKeyMap['d']) {
      this.playerVelocity.add(cameraRight.multiplyScalar(speedDelta))
    }
    // 跳跃
    if (this.pressedKeyMap[' '] && this.enableJump && this.playerOnFloor) {
      this.playerVelocity.y = JUMP_VELOCITY
    }
  }

  // 更新玩家
  updatePlayer(deltaTime: number) {
    let damping = Math.exp(-4 * deltaTime) - 1
    if (!this.playerOnFloor) {
      this.playerVelocity.y -= GRAVITY * deltaTime
      damping *= 0.1
    }
    this.playerVelocity.addScaledVector(this.playerVelocity, damping)

    const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime)
    this.playerCollider.translate(deltaPosition)
    this.playerCollisions()
    eyePos.addVectors(this.playerCollider.end, colliderEndToEye)
    this.camera.position.copy(eyePos)
  }

  // 如果玩家脱离世界，传送到原点
  teleportPlayerIfOob() {
    if (this.camera.position.y <= -25)
      this.teleportPlayerTo(0, playerHeight - distEyeToTop, 0, 0, 0)
  }
  teleportPlayerTo(eyeX: number, eyeY: number, eyeZ: number, rx = 0, ry = 0) {
    const footY = eyeY - playerHeight - distEyeToTop
    this.playerCollider.start.set(eyeX, footY + playerRadius, eyeZ)
    this.playerCollider.end.set(eyeX, footY + playerHeight - playerRadius, eyeZ)
    this.playerCollider.radius = playerRadius
    eyePos.addVectors(this.playerCollider.end, colliderEndToEye)
    this.camera.position.copy(eyePos)
    this.camera.rotation.set(rx, ry, 0)
  }

  // 鼠标
  isMouseLeft = false
  onMouseDown: (e: MouseEvent) => void
  onMouseUp: (e: MouseEvent) => void
  onMouseMove: (e: MouseEvent) => void
  _onMouseDown(e: MouseEvent) {
    if (!this.enabled) return

    // console.log('[TourControls] onMouseDown')
    if (e.buttons == 1) this.isMouseLeft = true
  }
  _onMouseUp(e: MouseEvent) {
    if (!this.enabled) return

    // console.log('[TourControls] onMouseUp')
    this.isMouseLeft = false
  }
  _onMouseMove(e: MouseEvent) {
    if (!this.enabled) return

    // console.log('[TourControls] onMouseMove', this.isMouseLeft)
    if (this.isMouseLeft) {
      // console.log('拖拽相机', e.movementX, e.movementY)
      this.camera.rotation.y += e.movementX / 600
      this.camera.rotation.x += e.movementY / 600
    }
  }

  // 键盘
  pressedKeyMap: { [key: string]: boolean } = {}
  onKeyDown: (e: KeyboardEvent) => void
  onKeyUp: (e: KeyboardEvent) => void
  _onKeyDown(e: KeyboardEvent) {
    if (!this.enabled) return

    // console.log('[TourControls] onKeyDown', e.key)
    this.pressedKeyMap[e.key] = true
  }
  _onKeyUp(e: KeyboardEvent) {
    if (!this.enabled) return

    // console.log('[TourControls] onKeyUp', e.key)
    this.pressedKeyMap[e.key] = false
  }

  update() {
    if (this.baked == false || this.flying) return

    const deltaTime = this.clock.getDelta()
    this.updateControls(deltaTime)
    this.updatePlayer(deltaTime)
    this.teleportPlayerIfOob()
  }
}
